package cn.jingyuan.owl.utils.core.text.csv;

import cn.jingyuan.owl.utils.core.ArrayUtils;
import cn.jingyuan.owl.utils.core.CharUtils;
import cn.jingyuan.owl.utils.core.CharsetUtils;
import cn.jingyuan.owl.utils.core.ObjectUtils;
import cn.jingyuan.owl.utils.core.collection.CollectionUtils;
import cn.jingyuan.owl.utils.core.convert.Convert;
import cn.jingyuan.owl.utils.core.io.FileUtils;
import cn.jingyuan.owl.utils.core.io.IORuntimeException;
import cn.jingyuan.owl.utils.core.io.IoUtils;

import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.Flushable;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collection;

/**
 * CSV 数据写出器
 */
public final class CsvWriter implements Closeable, Flushable, Serializable {

    /** 写出器 */
    private final Writer writer;

    /** 写出配置 */
    private final CsvWriteConfig config;

    /** 是否处于新行开始 */
    private boolean newline = true;

    /**
     * 构造，覆盖已有文件（如果存在），默认编码 UTF-8
     *
     * @param filePath File CSV 文件路径
     */
    public CsvWriter(String filePath) {
        this(FileUtils.file(filePath));
    }

    /**
     * 构造，覆盖已有文件（如果存在），默认编码 UTF-8
     *
     * @param file File CSV 文件
     */
    public CsvWriter(File file) {
        this(file, CharsetUtils.CHARSET_UTF_8);
    }

    /**
     * 构造，覆盖已有文件（如果存在）
     *
     * @param filePath File CSV 文件路径
     * @param charset 编码
     */
    public CsvWriter(String filePath, Charset charset) {
        this(FileUtils.file(filePath), charset);
    }

    /**
     * 构造，覆盖已有文件（如果存在）
     *
     * @param file File CSV 文件
     * @param charset 编码
     */
    public CsvWriter(File file, Charset charset) {
        this(file, charset, false);
    }

    /**
     * 构造
     *
     * @param filePath File CSV 文件路径
     * @param charset 编码
     * @param isAppend 是否追加
     */
    public CsvWriter(String filePath, Charset charset, boolean isAppend) {
        this(FileUtils.file(filePath), charset, isAppend);
    }

    /**
     * 构造
     *
     * @param file CSV 文件
     * @param charset 编码
     * @param isAppend 是否追加
     */
    public CsvWriter(File file, Charset charset, boolean isAppend) {
        this(file, charset, isAppend, null);
    }

    /**
     * 构造
     *
     * @param filePath CSV 文件路径
     * @param charset 编码
     * @param isAppend 是否追加
     * @param config 写出配置，null 则使用默认配置
     */
    public CsvWriter(String filePath, Charset charset, boolean isAppend, CsvWriteConfig config) {
        this(FileUtils.file(filePath), charset, isAppend, config);
    }

    /**
     * 构造
     *
     * @param file CSV 文件
     * @param charset 编码
     * @param isAppend 是否追加
     * @param config 写出配置，null 则使用默认配置
     */
    public CsvWriter(File file, Charset charset, boolean isAppend, CsvWriteConfig config) {
        this(FileUtils.getWriter(file, charset, isAppend), config);
    }

    /**
     * 构造，使用默认配置
     *
     * @param writer {@link Writer}
     */
    public CsvWriter(Writer writer) {
        this(writer, null);
    }

    /**
     * 构造
     *
     * @param writer Writer
     * @param config 写出配置，null 则使用默认配置
     */
    public CsvWriter(Writer writer, CsvWriteConfig config) {
        this.writer = (writer instanceof BufferedWriter) ? writer : new BufferedWriter(writer);
        this.config = ObjectUtils.defaultIfNull(config, CsvWriteConfig.defaultConfig());
    }

    /**
     * 设置是否始终使用文本分隔符，文本包装符，默认 false，按需添加
     *
     * @param alwaysDelimitText 是否始终使用文本分隔符，文本包装符，默认 false，按需添加
     *
     * @return this
     */
    public CsvWriter setAlwaysDelimitText(boolean alwaysDelimitText) {
        this.config.setAlwaysDelimitText(alwaysDelimitText);
        return this;
    }

    /**
     * 设置换行符
     *
     * @param lineDelimiter 换行符
     *
     * @return this
     */
    public CsvWriter setLineDelimiter(char[] lineDelimiter) {
        this.config.setLineDelimiter(lineDelimiter);
        return this;
    }

    /**
     * 将多行写出到 Writer
     *
     * @param lines 多行数据
     *
     * @return this
     *
     * @throws IORuntimeException IO 异常
     */
    public CsvWriter write(String[]... lines) throws IORuntimeException {
        if (ArrayUtils.isNotEmpty(lines)) {
            for (final String[] values : lines) {
                appendLine(values);
            }
            flush();
        }
        return this;
    }

    /**
     * 将多行写出到 Writer
     *
     * @param lines 多行数据，每行数据可以是集合或者数组
     *
     * @return this
     *
     * @throws IORuntimeException IO 异常
     */
    public CsvWriter write(Collection<?> lines) throws IORuntimeException {
        if (CollectionUtils.isNotEmpty(lines)) {
            for (Object values : lines) {
                appendLine(Convert.toStrArray(values));
            }
            flush();
        }
        return this;
    }

    /**
     * 追加新行（换行）
     *
     * @throws IORuntimeException IO 异常
     */
    public void writeLine() throws IORuntimeException {
        try {
            writer.write(config.lineDelimiter);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        newline = true;
    }

    @Override
    public void close() {
        IoUtils.close(this.writer);
    }

    @Override
    public void flush() throws IORuntimeException {
        try {
            writer.flush();
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 追加一行，末尾会自动换行，但是追加前不会换行
     *
     * @param fields 字段列表 ({@code null} 值会被做为空值追加)
     *
     * @throws IORuntimeException IO 异常
     */
    private void appendLine(final String... fields) throws IORuntimeException {
        try {
            doAppendLine(fields);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 追加一行，末尾会自动换行，但是追加前不会换行
     *
     * @param fields 字段列表 ({@code null} 值会被做为空值追加)
     *
     * @throws IOException IO 异常
     */
    private void doAppendLine(final String... fields) throws IOException {
        if (null != fields) {
            for (int i = 0; i < fields.length; i++) {
                appendField(fields[i]);
            }
            writer.write(config.lineDelimiter);
            newline = true;
        }
    }

    /**
     * 在当前行追加字段值，自动添加字段分隔符，如果有必要，自动包装字段
     *
     * @param value 字段值，{@code null} 会被做为空串写出
     *
     * @throws IOException IO 异常
     */
    private void appendField(final String value) throws IOException {
        boolean alwaysDelimitText = config.alwaysDelimitText;
        char textDelimiter = config.textDelimiter;
        char fieldSeparator = config.fieldSeparator;

        if (newline) {
            newline = false;
        } else {
            writer.write(fieldSeparator);
        }

        if (null == value) {
            if (alwaysDelimitText) {
                writer.write(new char[]{textDelimiter, textDelimiter});
            }
            return;
        }

        final char[] valueChars = value.toCharArray();
        boolean needsTextDelimiter = alwaysDelimitText;
        boolean containsTextDelimiter = false;

        for (final char c : valueChars) {
            if (c == textDelimiter) {
                // 字段值中存在包装符
                containsTextDelimiter = needsTextDelimiter = true;
                break;
            } else if (c == fieldSeparator || c == CharUtils.LF || c == CharUtils.CR) {
                // 包含分隔符或换行符需要包装符包装
                needsTextDelimiter = true;
            }
        }

        // 包装符开始
        if (needsTextDelimiter) {
            writer.write(textDelimiter);
        }

        // 正文
        if (containsTextDelimiter) {
            for (final char c : valueChars) {
                // 转义文本包装符
                if (c == textDelimiter) {
                    writer.write(textDelimiter);
                }
                writer.write(c);
            }
        } else {
            writer.write(valueChars);
        }

        // 包装符结尾
        if (needsTextDelimiter) {
            writer.write(textDelimiter);
        }
    }

}
